package com.everspring.util;

import com.lowagie.text.pdf.BaseFont;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * @desc: 通过thymeleaf模板生成pdf
 * @author: EverSpring
 */
@Slf4j
public class PdfThymeleafUtils extends PdfWrapper{

    /**
     * 生成pdf文件字节数组，组件自带模板引擎配置
     * 可以通过IOUtils.write(final byte[] data, final OutputStream output)等方式输出成文件
     * @param templateName 模板名称
     * @param listParamMap 参数
     * @return pdf字节流
     * @throws Exception
     */
    public static byte[] export(String templateName, List<Map<String, Object>> listParamMap) throws Exception {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            generatePdf(initTemplateEngine(), templateName, listParamMap, out);
            return out.toByteArray();
        }
    }

    /**
     * 生成pdf文件字节数组
     * 可以通过IOUtils.write(final byte[] data, final OutputStream output)等方式输出成文件
     * @param templateEngine 模板引擎配置
     * @param templateName 模板名称
     * @param listParamMap 参数
     * @return pdf字节流
     * @throws Exception
     */
    public static byte[] export(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listParamMap) throws Exception {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            generatePdf(templateEngine, templateName, listParamMap, out);
            return out.toByteArray();
        }
    }

    /**
     * 转换输出pdf到特定位置，默认模板引擎
     *
     * @param templateName   模板名称
     * @param listVars       模板参数集
     * @param outPath       输出pdf文件路径
     */
    public static void export(String templateName, List<Map<String, Object>> listVars, String outPath) throws Exception {
        try (OutputStream out = new FileOutputStream(outPath)) {
            generatePdf(initTemplateEngine(), templateName, listVars, out);
            out.flush();
        }
    }

    /**
     * 转换输出pdf到特定位置
     *
     * @param templateEngine 模板引擎配置
     * @param templateName   模板名称
     * @param listVars       模板参数集
     * @param outPath       输出pdf文件路径
     */
    public static void export(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String outPath) throws Exception {
        try (OutputStream out = new FileOutputStream(outPath)) {
            generatePdf(templateEngine, templateName, listVars, out);
            out.flush();
        }
    }

    /**
     * 核心: 根据模板生成pdf文档
     *
     * @param templateEngine 配置
     * @param templateName   模板名称
     * @param listVars       模板参数
     * @param out            输出流
     * @throws Exception 模板无法找到、模板语法错误、IO异常
     */
    private static void generatePdf(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, OutputStream out) throws IOException, ParserConfigurationException, SAXException {
        ITextRenderer renderer = new ITextRenderer();
        try {
            //设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"
            ITextFontResolver fontResolver = renderer.getFontResolver();
            //避免中文为空设置系统字体
            String fontPath = getFontPath();
            log.info("thymeleaf font location:{}", fontPath);
            fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            // 解决图片的相对路径问题
            String baseImgDir = getBaseImgDir();
            if (StringUtils.hasText(baseImgDir)) {
                renderer.getSharedContext().setBaseURL(baseImgDir);
            }

            //根据参数集个数循环调用模板,追加到同一个pdf文档中，i=0是创建pdf,从1往后则向pdf中追加内容
            for (int i = 0; i < listVars.size(); i++) {
                Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));
                renderer.setDocument(docAppend, null);
                // 展现和输出pdf
                renderer.layout();
                if (i == 0) {
                    renderer.createPDF(out, false);
                } else {
                    //写下一个pdf页面
                    renderer.writeNextDocument();
                }
            }
        } finally {
            renderer.finishPDF(); //完成pdf写入
        }
    }

    /**
     * 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
     *
     * @param templateEngine 模板引擎
     * @param templateName 模板名称
     * @param variables    模板参数
     * @return Document
     */
    private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables) throws IOException, ParserConfigurationException, SAXException {
        // 声明一个上下文对象，放入要存到模板里面的数据
        final Context context = new Context();
        context.setVariables(variables);
        StringWriter stringWriter = new StringWriter();
        try (BufferedWriter writer = new BufferedWriter(stringWriter)) {
            templateEngine.process(templateName, context, writer);
            writer.flush();
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            String s = stringWriter.toString();
            return builder.parse(new ByteArrayInputStream(s.getBytes()));
        } catch (Exception e) {
            log.error("thymeleaf模板转pdf失败", e);
            throw e;
        }
    }

    /**
     * 初始化模板引擎
     * @return
     */
    private static TemplateEngine initTemplateEngine() {
        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        // 非容器启动注意prefix
        templateResolver.setPrefix(getTemplateDirPath());
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);

        TemplateEngine templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }
}
